/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.bindings;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.balisunrise.annotation.util.ReflectUtilities;
import org.balisunrise.common.BindingException;

/**
 * Leitor de Propriedades de um Objeto.
 *
 * @author Glauco
 */
public class BeansPropertyReader {

    /**
     * Instâncias de escritores de propriedade
     */
    private static Map<Class,BeansPropertyReader> instances =
            new HashMap<Class, BeansPropertyReader>();

    /**
     * Classe do objeto que deve ser escrita as propriedades.
     */
    private Class objectClass;

    /**
     * Util para pegar os métodos.
     */
    private ReflectUtilities ru;

    /**
     * Métodos Set das propriedades.
     */
    private Map<String,Method> methods;

    public BeansPropertyReader(Class objectClass) {
        this.objectClass = objectClass;
        ru = new ReflectUtilities(objectClass);
        methods = new HashMap<String, Method>();
    }

    /**
     * Pega uma instância de Leitor de propriedade para um determinado
     * objeto.<BR>
     * Se o Objeto for do tipo Class, é utilizado este tipo de dado para fazer
     * a escrita das propriedades, caso contrario, é pego o tipo de dado do
     * objeto.
     *
     * @param o Objeto, o qual deve ser feita a escrita dos dados.
     */
    private static BeansPropertyReader getInstance(Object o){

        if (o == null)
            throw new NullPointerException(
                    "BeansPropertyReader#getInstance received a null parm.");

        // pega a classe
        Class clazz;
        if (o instanceof Class )
            clazz = (Class) o;
        else
            clazz = o.getClass();

        // verifica se já exite uma instância.
        if(instances.containsKey(clazz))
            return instances.get(clazz);

        // Cria nova instância
        BeansPropertyReader instance = new BeansPropertyReader(clazz);
        instances.put(clazz, instance);

        return instance;
    }

    /**
     * Retorna o Método Get de uma propriedade.
     * @param property propriedade.
     * @return Method.
     */
    private Method getMethod(String property) throws BindingException{

        if(methods.containsKey(property))
            return methods.get(property);

        Method m = ru.getGetMethod(property);

        if(m == null)
            throw new BindingException(
                    "BeansPropertyWriter can't get a property: " +
                    objectClass.getName() + " don't have a Get Method for \"" +
                    property + "\"");

        methods.put(property, m);

        return m;
    }

    /**
     * Le o valor da propriedade do source.
     * @param source Objeto com a propriedade que deve lido o valor.
     * @param property Nome da propriedade.
     */
    public static Object getProperty(Object source, String property)
            throws PropertyReaderException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException,
            BindingException{

        String[] props;
        if(property.contains("."))
            props = property.split("\\.");
        else
            props = new String[]{property};

        Object value = source;

        for (int i = 0; i < props.length; i++) {

            String prop = props[i];

            if(value == null)
                throw new PropertyReaderException(null);

            BeansPropertyReader bpr = getInstance(value);
            Method m = bpr.getMethod(prop);
            value = m.invoke(value);
        }

        return value;
    }

    /**
     * Exception for null source.
     */
    public static class PropertyReaderException extends Exception{
        public PropertyReaderException(Throwable cause) {
            super("A null source founded.", cause);
        }
    }
}
